/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-12-10     xu       the first version
 */
#include "key_task.h"
#include "message_type.h"

#define DBG_TAG "key"
#define DBG_LVL DBG_LOG
#include <drivers/pin.h>
#include <rtdbg.h>
#include <rtthread.h>

#define KEY_MQ_MAX_MSGS 100
#define KEY_MQ_MSG_SIZE 1
#define KEY_TASK_TICKS 1
#define KEY_TASK_PRIORITY 9
#define KEY_TASK_STACK_SIZE 2048

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))

struct key_context {
    enum key_id id;
    const char* name;
    rt_base_t pin;
    int last_state;
};

#define PIN_INVALID -1

static struct key_context board_key_array[] = {
    {.id = ID_KEY1, .name = "PA.4", .pin = PIN_INVALID, .last_state = PIN_HIGH},
    {.id = ID_KEY2, .name = "PA.5", .pin = PIN_INVALID, .last_state = PIN_HIGH},
    {.id = ID_KEY3, .name = "PA.6", .pin = PIN_INVALID, .last_state = PIN_HIGH},
};

#define BOARD_KEY_NUMS ARRAY_SIZE(board_key_array)

static rt_bool_t init_board_key()
{
    rt_bool_t retval = RT_TRUE;
    for (rt_size_t i = 0; i < BOARD_KEY_NUMS; i++) {
        rt_base_t pin = rt_pin_get(board_key_array[i].name);
        if (pin < 0) {
            LOG_E("get pin %s id failed!", board_key_array[i].name);
            retval = RT_FALSE;
        }
        rt_pin_mode(pin, PIN_MODE_INPUT_PULLUP);
        board_key_array[i].pin = pin;
    }
    LOG_I("board key array init done!");
    return retval;
}

static int poll_key_event(struct key_event* message, rt_uint32_t index)
{
    if (message == RT_NULL) {
        LOG_E("%s: message null!", __func__);
        return -1;
    }
    if (index >= BOARD_KEY_NUMS) {
        LOG_E("%s: index out of range: %d!", __func__, index);
        return -1;
    }

    int state = rt_pin_read(board_key_array[index].pin);
    if (state != board_key_array[index].last_state) {
        message->key = board_key_array[index].id;
        message->state = (state == PIN_LOW) ? STATE_PRESSED : STATE_RELEASE;
        board_key_array[index].last_state = state;
        // LOG_D("Key message raised, key = %d, state = %d", message->key, message->state);
        return 1;
    }
    return 0; // means no message raised on current key
}

static void key_entry(void* param)
{
    key_task_t context = (key_task_t)param;
    if (context == RT_NULL) {
        LOG_E("%s: context null!", __func__);
        return;
    }

    if (init_board_key() != RT_TRUE) {
        LOG_E("%s: board key array init failed!", __func__);
        return;
    }

    RT_ASSERT(context->mq);
    LOG_I("key array scan task start...");

    struct message* message = rt_malloc(sizeof(struct message));
    if (message == RT_NULL) {
        LOG_E("%s: alloc message failed!", __func__);
        return;
    }

    message->type = MSG_KEY_EVENT;
    while (context->running) {
        rt_thread_mdelay(context->scan_ms);
        for (rt_size_t i = 0; i < BOARD_KEY_NUMS; i++) {
            int count = poll_key_event(&message->data.key_event, i);
            if (count > 0) {
                rt_err_t err = rt_mq_send(context->mq, message, sizeof(*message));
                if (err != RT_EOK) {
                    LOG_E("%s: send key_event %d to mq failed!", __func__, message->data.key_event.key);
                }
            }
        }
    }
    LOG_I("key array scan work finish!");

    rt_free(message);
    rt_err_t err = rt_sem_release(context->sem);
    if (err != RT_EOK) {
        LOG_E("raise work finish signal to sem failed!");
    }
    LOG_I("key array scan task done!...");
}

key_task_t key_task_create(rt_mq_t mq, rt_uint32_t scan_ms)
{
    if (mq == RT_NULL) {
        LOG_E("mq null!", mq);
        return RT_NULL;
    }

    key_task_t context = rt_malloc(sizeof(struct key_task));
    if (context == RT_NULL) {
        LOG_E("%s: alloc context failed!", __func__);
        return RT_NULL;
    }

    const char* name = "key";
    rt_sem_t sem = rt_sem_create(name, 0, RT_IPC_FLAG_FIFO);
    if (sem == RT_NULL) {
        LOG_E("create sem %s failed!", name);
        rt_free(context);
        return RT_NULL;
    }

    rt_thread_t thread =
        rt_thread_create(name, key_entry, context, KEY_TASK_STACK_SIZE, KEY_TASK_PRIORITY, KEY_TASK_TICKS);
    if (thread == RT_NULL) {
        LOG_E("create thread %s failed!", name);
        rt_sem_delete(sem);
        rt_free(context);
        return RT_NULL;
    }

    context->mq = mq;
    context->name = name;
    context->scan_ms = scan_ms;
    context->running = RT_TRUE;
    context->thread = thread;
    context->sem = sem;
    LOG_I("key task create done!");
    return context;
}

rt_bool_t key_task_start(key_task_t context)
{
    if (context == RT_NULL) {
        LOG_E("%s: context null!", __func__);
        return RT_FALSE;
    }

    rt_err_t err = rt_thread_startup(context->thread);
    if (err != RT_EOK) {
        LOG_E("start thread %s failed!", context->name);
        return RT_FALSE;
    }
    LOG_I("key task start done!");
    return RT_TRUE;
}

rt_bool_t key_task_stop(key_task_t context)
{
    if (context == RT_NULL) {
        LOG_E("%s: context null!", __func__);
        return RT_FALSE;
    }

    context->running = RT_FALSE;
    rt_thread_yield();

    rt_err_t err = rt_sem_take(context->sem, RT_WAITING_FOREVER);
    if (err != RT_EOK) {
        LOG_E("wait %s task stop failed!", context->name);
        return RT_FALSE;
    }

    LOG_I("key task stop done!");
    return RT_TRUE;
}

rt_bool_t key_task_destroy(key_task_t context)
{
    rt_err_t err = 0;
    if (context == RT_NULL) {
        LOG_E("%s: context null!", __func__);
        return RT_FALSE;
    }

    err |= rt_sem_delete(context->sem);
    err |= rt_thread_delete(context->thread);
    rt_free(context);

    if (err != RT_EOK) {
        LOG_E("%s: some error raised!", context->name);
        return RT_FALSE;
    }
    LOG_I("key task destroy done!");
    return RT_TRUE;
}